package eval.engine.cplex.InitialCplexCode;

import ilog.opl.IloCustomOplDataSource;
import ilog.opl.IloOplFactory;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.Properties;
import java.util.logging.Logger;

public class CplexDataSource extends IloCustomOplDataSource {


	final static Class<CplexDataSource> sCLASS = CplexDataSource.class;
	final static String CLASSNAME = sCLASS.getName(); 
	final static Logger sLOGGER = Logger.getLogger("vsilogger.debug");
	final static Logger essentialLogger = Logger.getLogger("vsilogger.debug");
	
	// All the names here should be the names of the OPL Data Model data
	// members!
	static final String CPLEX_PROPERTY_PREFIX      = "cplex.props.";

	static final String REWARD_WEIGHT_NAME         = "REWARD_WEIGHT";
	static final String PLACEMENT_WEIGHT_NAME      = "PLACEMENT_WEIGHT";
	static final String RED_LINE_WEIGHT_NAME       = "RED_LINE_WEIGHT";
	static final String ENERGY_WEIGHT_NAME         = "ENERGY_WEIGHT";
	static final String CAPACITY_OVERFLOW_WEIGHT_NAME = "CAPACITY_OVERFLOW_WEIGHT";
	static final String RELOCATION_WEIGHT_NAME     = "RELOCATION_WEIGHT";

	private static final String USE_CPLEX_JNI = "useCplexJNI";

	// Piecewise function:
	private static final String NBPIECES = "nbPieces";
	
	private static final String LOADBALANCINGACTIVATED = "loadBalancingActivated";
	private static final String LOADCOSTSLOPES = "loadCostSlopes";
	private static final String LOADCBREAKPOINTS = "loadBreakpoints";
	private static final String CURRENT_LB_SCORE = "currentLoadBalancingScore";
	private static final String IDEAL_LB_SCORE = "idealLoadBalancingScore";

	private static final int RESOURCE_DEMAND_INDEX = 0;   // the sole resource for which the dynamic demand is taken into consideration
	private static final String CUSTOM_READ_LOG = " CUSTOM READ";
	private static final String CPLEX_DATA_FILE_PREFIX = " CPLEX DATA INPUT\n";
	/**
	 * the maximal load of a VM so that it will load balance with a 100% loaded VM 
	 * by exchanging a single standard VM between them.
	 */
	static final String RELOCATED_LOAD = "relocated.load";  
	/**
	 * The maximal load of a standard host so that it will load balance a standard VM with a standard host loaded at 0%
	 */
	static final String UPPER_BOUND_NORMAL_RANGE  = "upper.bound.normal.range";   
	/**
	 * The ratio between a standard VM and a standard host
	 */
	static final String STANDARD_MINIMAL_UTILIZATION_GAP  = "minimal.utilization.gap";   

	static final float DEFAULT_RELOCATED_LOAD           = 0.05f;   // between 0.0 and 1.0
	static final float DEFAULT_UPPER_BOUND_NORMAL_RANGE = 0.60f;   // between 0.0 and 1.0
	static final float DEFAULT_MINIMAL_UTILIZATION_GAP  = 0.20f;   // between 0.0 and 1.0

	// should be at least 2:
	private final static int PIECES_IN_NORMAL_RANGE = 12; // number of pieces for the piecewise approximation in the normal range.
	private final static int PIECES_IN_UPPER_RANGE  = 20; // this is the number of pieces for the piecewise approximation in the normal range
	private static final String CPLEX_OPL_INPUT_HEADER = "Input data for OPL, jobId=";


	// the number of segments for the piecewise linear function
	protected int nbPieces = PIECES_IN_NORMAL_RANGE + PIECES_IN_UPPER_RANGE;

	// default value for load relocated between two hosts to reduce the gap between them (when a VM is relocated between them)  
	float relocatedLoad;
	// upper bound for normal range. No relocation (of average VM or bigger, 
	// that account for relocated.load load or smaller) in the range [0, upper.bound.normal.range[
	float upperBoundNormalRange;

	// minimal utilization gap between a 100% host and another host  to trigger relocation 
	// (of average VM or smaller, that account for relocated.load or bigger load)
	float utilizationMinimalGap;

	// The square coefficient for the normal range modelization of the load cost
	Double normalRangeSquareCoef = null; 

	// the X**3 coefficient for the upper range modelization of the load cost:
	Double highRangeLevel3Coef = null;
	
	private Properties props;
	private Properties oplProps;
	private Boolean useCplexJNI = null;  // if true data will be passed to CPLEX thru multiple calls of the JNI interface else it will be passed as a file 
	private StringBuffer sbFile = null;

	CplexDataSource(/*Context planContext*/) {
		super(new IloOplFactory());
	}

	public CplexDataSource(final IloOplFactory oplFactory, String cplexInputString) {
		super(oplFactory);

		// if useCplexJNI is false the input to CPLEX will consist of an input.dat file, the multiple JNI will not be invoked 
		useCplexJNI = false; // new Boolean(props.getProperty((CPLEX_PROPERTY_PREFIX + USE_CPLEX_JNI)).trim());

		sbFile = new StringBuffer(cplexInputString);

//		readLoadBalancingParameters();
//		computeCostFunctionParameters();
	}

//	/**
//	 * Transform the input string into a Property instance
//	 * @param theString the OPL data input string 
//	 * @return the constructed Property instance
//	 */
//	private Properties getProperties(String theString) {
//		Properties retProps = new Properties();
//		
//		String lines[] = theString.split(END_OF_LINE);
//		
//		for (String nextLine : lines) {
//			nextLine = nextLine.trim();
//			if (nextLine.startsWith(OPL_COMMENT) || nextLine.isEmpty()) {
//				// NOP
//			} else {
//				String[] lineParts = nextLine.split(OPL_PROP_EQUAL);
//				if (lineParts.length != 2) {
//					throw new CplexDataSourceException(" Bad input line:" + nextLine);
//				} else {
//					retProps.setProperty(lineParts[0], lineParts[1]);
//				}
//			}
//		}
//		
//		return retProps;
//	}

//	/**
//	 * Init data structures that are not related to PVU licensing 
//	 */
//	private void initVariables() {
//
//		nbResources = ResourceType.values().length;
//		previousPlacement = new int[nbVM][nbHosts];
//		currentHostCpuLoad = new double[nbHosts];
//		
//		capacity = new int[nbHosts][nbResources];
//		reservation = new int[nbVM][nbResources];
//
//		fixed = new Integer[nbVM];
//		initArray(fixed, 0);
//	}

//	private <T> void initArray(T[] array, T initValue) {
//		for (int index=0; index<array.length; index++) {
//			array[index] = initValue;
//		}
//	}

	/*
	 * (non-Javadoc)
	 * 
	 * Automatically invoked by the CPLEX engine
	 * 
	 * @see ilog.opl.IloCustomOplDataSource#customRead()
	 */
	@Override
	public void customRead() {
		writeDataFile();
	}

//	private String dumpTranslationTables() {
//		
//		final StringBuffer translationSb = new StringBuffer();
//		
//		{
//			// Add physical translation map:
//			translationSb.append(OPL_COMMENT + "Physical translation map:  PEID -> Cplex index \n");
//			PhysicalConfiguration pc = this.environment.getPhysicalConfiguration();
//			for (PhysicalEntityID nextPeId : EnvUtil.allPEIDs(pc)) {
//				translationSb.append(OPL_COMMENT + nextPeId + "->" + getPePosition(nextPeId) + "\n");
//			}
//			translationSb.append(OPL_COMMENT + "\n");
//		}
//		{	
//			// Add virtual translation map:
//			translationSb.append(OPL_COMMENT + "Virtual translation map:   VEID -> Cplex index \n");
//			VirtualConfiguration vc = this.environment.getVirtualConfiguration();
//			for (VirtualEntityID nextVeId : EnvUtil.allVEIDs(vc)) {
//				translationSb.append(OPL_COMMENT + nextVeId + "->" + getVePosition(nextVeId) + "\n");
//			}
//			translationSb.append(OPL_COMMENT + "\n");
//	c	}
//		return translationSb.toString();
//	}


	private void writeDataFile() {
		
//		String translationTables = null;
		if (isCplexInputLoggable()) {
			StringBuffer headerSb = new StringBuffer();
			headerSb.append(CPLEX_OPL_INPUT_HEADER);
//			translationTables = dumpTranslationTables(); 
//			headerSb.append(translationTables);
			
			essentialLogger.log(CplexAdapter.cplexInputJournalLevel, headerSb.toString() + sbFile.toString());
		}

		if (CplexAdapter.COLLECT_CPLEX_FILES_TO_FILE) {
			// In any case, write content of input file to log:
			sLOGGER.finest(CPLEX_DATA_FILE_PREFIX + sbFile.toString());

			// Write input file to file system if needed:
			if (useCplexJNI) {
				sLOGGER.info("Using cplex JNI multiple invocations mode");
			} else {
//				if (translationTables == null)
//					translationTables = dumpTranslationTables();
				
				sLOGGER.info("Using cplex all in one file JNI invocations mode");
				String directoryPath = CplexAdapter.DIRECTORY_PATH;
				if (directoryPath != null && directoryPath.length()>0) {
				  long jobId = System.currentTimeMillis();
				  String thePath = directoryPath + generateNewDataFileName(jobId);
				  stringToFile(thePath, /*translationTables + */ sbFile.toString());
				}
			}
		} 
	}

	public static void stringToFile(String filePath, String fileContent) {
		try{
			FileWriter fstream = new FileWriter(filePath);
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(fileContent);
			out.close();
		}catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public String getDataString() {
		return sbFile.toString();
	}

	/**
	 * Returns true if JNI multiple invocations are to be used to send input data to OPL model
	 */
	public boolean isUseCplexJNI() {
		return useCplexJNI;
	}

	/**
	 * @return a unique Cplex input data file name based on the jobId and system time 
	 */
	public String generateNewDataFileName() {
		return generateNewDataFileName(0);
	}
	
	/**
	 * @return a unique Cplex input data file name based on the jobId and system time 
	 */
	public String generateNewDataFileName(long jobId) {
		String jobIdString = "";
		if (jobId != 0) {
			jobIdString = "_" + jobId;
		}
	  	String fullPath = CplexAdapter.FILE_BASE_NAME + "_" + CplexPlacementModel.getFileIndexString() + jobIdString + "_" + System.currentTimeMillis();
		sLOGGER.info(" latest file is " + fullPath);
		
		return fullPath;
	}

	private boolean isCplexInputLoggable() {
		return true;
	}

	
}
